package com.se.http;

import com.util.FileUtil;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.GZIPInputStream;

/**
 * @Author: hdu_huang
 * @Date: 2021/7/23 15:15
 * 基于HttpURLConnection的HttpClient
 */
@Slf4j
public class HttpV1Client {

    public static final int WRITE_REDIRECT_CODE = 307;
    public static final String CONTENT_ENCODING = "Content-Encoding";

    public static final int READ_TIMEOUT_MILLIS = Integer.getInteger("simple.v1.http.read.timeout", 5000);
    public static final int CONNECT_TIMEOUT_MILLIS = Integer.getInteger("simple.v1.http.connect.timeout", 3000);

    public static final String Default_Charset = StandardCharsets.UTF_8.name();

    static {
        // limit max redirection
//        System.setProperty("http.maxRedirects", "5");
    }


    public static boolean stringIsEmpty(String str) {
        return str == null || str.isEmpty();
    }

    public static final String EMPTY = "";

    public static HashMap<String, String> builderHeaders() {
        HashMap<String, String> headers = new HashMap<>();

        headers.put("Accept-Encoding", "gzip,deflate,sdch");
        headers.put("Connection", "Keep-Alive");
        headers.put("RequestId", UUID.randomUUID().toString());
        headers.put("Request-Module", "JDK5-Client");
        return headers;
    }

    public static HttpResult get(String url, Map<String, String> params) {
        return requestForm(url, null, params, Default_Charset, "GET", -1);
    }

    public static HttpResult get(String url, Map<String, String> headers, Map<String, String> params) {
        return requestForm(url, headers, params, Default_Charset, "GET", -1);
    }

    public static HttpResult get(String url, Map<String, String> headers, Map<String, String> params, String encoding, int readTimeout) {
        return requestForm(url, headers, params, encoding, "GET", readTimeout);
    }

    public static HttpResult postForm(String url, Map<String, String> headers, Map<String, String> params) {
        return requestForm(url, headers, params, Default_Charset, "POST", -1);
    }

    public static HttpResult postForm(String url, Map<String, String> headers, Map<String, String> params, String encoding, int readTimeout) {
        return requestForm(url, headers, params, encoding, "POST", readTimeout);
    }

    public static HttpResult postJson(String url, Map<String, String> headers, String params) {
        return requestRaw(url, headers, params, Default_Charset, "POST", -1);
    }

    public static HttpResult postXml(String url, Map<String, String> headers, String params) {

        if (headers == null) {
            headers = builderHeaders();
        }

        headers.put("Content-Type", "application/xml"); //xml
        return requestRaw(url, headers, params, Default_Charset, "POST", -1);
    }


    public static HttpResult requestForm(@NonNull String url, Map<String, String> headers, Map<String, String> params, String encoding, String method, int readTimeout) {
        HttpURLConnection conn = null;
        log.debug("requestForm, url:{}, method:{}, headers:{}, params:{}", url, method, headers, params);
        try {
            String encodedContent = encodingParams(params, encoding);
            url += (stringIsEmpty(encodedContent)) ? "" : ("?" + encodedContent);
            conn = (HttpURLConnection) new URL(url).openConnection();

            setHeaders(conn, headers, encoding);
            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=" + encoding);

            conn.setConnectTimeout(READ_TIMEOUT_MILLIS);
            conn.setReadTimeout(CONNECT_TIMEOUT_MILLIS);
            if (readTimeout >= 0) {
                conn.setReadTimeout(readTimeout);
            }

            conn.setRequestMethod(method);
            conn.setDoOutput(true);

            if (HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method)) {
                // fix: apache http nio framework must set some content to request body
                byte[] b = encodedContent.getBytes();
                conn.setRequestProperty("Content-Length", String.valueOf(b.length));
                conn.getOutputStream().write(b, 0, b.length);
                conn.getOutputStream().flush();
                conn.getOutputStream().close();
            }
            conn.connect();

            return getResult(conn);
        } catch (Exception e) {

            log.warn("requestForm error! url:{}, method:{}, headers:{}, params:{}", url, method, headers, params);
            return new HttpResult(500, e.toString(), Collections.<String, String>emptyMap());
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    //支持POST/PUT/DELETE方法   application/json xml  multipart/form-data  text/html plain
    public static HttpResult requestRaw(@NonNull String url, Map<String, String> headers, @NonNull String params, String encoding, @NonNull String method, int readTimeout) {

        log.debug("requestRaw, url:{}, method:{}, headers:{}, params:{}", url, method, headers, params);

        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();

            conn.setConnectTimeout(READ_TIMEOUT_MILLIS);
            conn.setReadTimeout(CONNECT_TIMEOUT_MILLIS);
            if (readTimeout >= 0) {
                conn.setReadTimeout(readTimeout);
            }
            conn.setRequestMethod(method);
            conn.setDoOutput(true);
            byte[] b = params.getBytes();

            conn.setRequestProperty("Content-Length", String.valueOf(b.length));
            conn.setRequestProperty("Content-Type", "application/json"); //默认json
            setHeaders(conn, headers, encoding);

            conn.getOutputStream().write(b, 0, b.length);
            conn.getOutputStream().flush();
            conn.getOutputStream().close();
            conn.connect();

            return getResult(conn);
        } catch (Exception e) {
            log.warn("requestRaw error! url:{}, method:{}, headers:{}, params:{}", url, method, headers, params);
            return new HttpResult(500, e.toString(), Collections.emptyMap());
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

    }

    public static HttpResult getResult(HttpURLConnection conn) throws IOException {
        int respCode = conn.getResponseCode();

        InputStream inputStream;
        if (HttpURLConnection.HTTP_OK == respCode
                || HttpURLConnection.HTTP_NOT_MODIFIED == respCode
                || WRITE_REDIRECT_CODE == respCode) {
            inputStream = conn.getInputStream();
        } else {
            inputStream = conn.getErrorStream();
        }

        Map<String, String> respHeaders = new HashMap<>(conn.getHeaderFields().size());
        for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
            respHeaders.put(entry.getKey(), entry.getValue().get(0));
        }

        String encodingGzip = "gzip";

        if (encodingGzip.equals(respHeaders.get(CONTENT_ENCODING))) {
            inputStream = new GZIPInputStream(inputStream);
        }

        return new HttpResult(respCode, FileUtil.toString(inputStream, getCharset(conn)), respHeaders);
    }

    private static String getCharset(HttpURLConnection conn) {
        String contentType = conn.getContentType();
        if (stringIsEmpty(contentType)) {
            return "UTF-8";
        }

        String[] values = contentType.split(";");
        if (values.length == 0) {
            return "UTF-8";
        }

        String charset = "UTF-8";
        for (String value : values) {
            value = value.trim();

            if (value.toLowerCase().startsWith("charset=")) {
                charset = value.substring("charset=".length());
            }
        }

        return charset;
    }


    private static void setHeaders(HttpURLConnection conn, Map<String, String> headers, String encoding) {

        if (headers == null) {
            headers = builderHeaders();
        }
        headers.forEach(conn::setRequestProperty);
        conn.addRequestProperty("Accept-Charset", encoding);
    }

    public static String encodingParams(Map<String, String> params, String encoding)
            throws UnsupportedEncodingException {
        if (null == params || params.isEmpty()) {
            return "";
        }

        params.put("encoding", encoding);
        StringBuilder sb = new StringBuilder();

        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (stringIsEmpty(entry.getValue())) {
                continue;
            }

            sb.append(entry.getKey()).append("=");
            sb.append(URLEncoder.encode(entry.getValue(), encoding));
            sb.append("&");
        }

        if (sb.length() > 0) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

}
